﻿#define  _CRT_SECURE_NO_WARNINGS 1
# include<iostream>
using namespace std;

// 子类的默认成员函数
class Person
{
public:
	// 如果这里没有缺省值，那么就不是默认构造函数，那么子类无法调用合适的父类的默认构造函数就会报错。
	Person(const string name = "Tom")
	{
 		cout << "Person()" << endl;
		_name = name;
	}

	Person(const Person& p)
	{
		cout << "Person显式拷贝构造" << endl;
		_name = p._name;
	}

	Person& operator=(const Person& p)
	{
		if (this != &p)
		{
			_name = p._name;
		}

		cout << "Person显式赋值运算符重载\n";
		return *this;
	}


	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	// 子类会先调用父类的构造函数，去构造自己父类的成员，在调用自己的构造函数
	Student(string name = "Tom", int id = 111)
		:Person(name) // 要想在子类构造函数去构造子类父类成员，必须调用显式调用父类的构造函数。
		,_id(id) 
	{
		cout << "Student()" << endl;
	}

	//子类的拷贝构造也不能直接修改父类成员，只能显式调用父类的拷贝构造
	Student(const Student& s)
		:Person(s) // 父类拷贝构造的形参是父类的对象，但是这里调用子类对象就行。前面说过这个叫切片
		,_id(s._id)
	{
		cout << "Student显式拷贝构造" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			// 调用父类的=运算符重载。传s是切片。
			Person::operator=(s); // 这里不能是this->operator(s); 
			// 因为与父类的operator=是同名函数，构成隐藏/重定义了。会无线调用自己，无限递归导致栈溢出
			_id = s._id;
		}

		cout << "Student显式赋值运算符重载\n";
		return *this;
	}

	~Student()
	{
		// ~Person和~Student也构成隐藏/重定义。因为编译器会把他们处理成destructor（跟多态有关）
		//Person::~Person(); // 因此这里不能写~Person，构成隐藏。要指定调用的是父类的析构函数
		// 析构函数虽然可以显式调用，但是实际上编译器在调用完子类的析构函数之后会自动的调用父类的析构函数。
		// 因此父类的析构函数不需要我们在子类显式调用
		cout << "~Student()" << endl;
	}

	 
protected:
	int _id;
};

// 设计一个不能被继承的类
class A 
{
private:
	// 构造或者析构私有化
	A() {}
};

class B : public A
{

};
 
int main()
{
	Student s1("jack", 222); // 先调用父类的构造函数，再调用自己的
	Student s2(s1); // 先调用父类的拷贝构造，再调用自己的

	Student s3;
	s1 = s3; 
	// 如果子类没有赋值运算符重载，那么会调用父类的赋值运算符重载，再调用系统生成的默认的子类的赋值运算符重载，是浅拷贝
	// 但是如果子类实现了，并且子类的赋值运算符重载中，没有调用父类的，就不会调用父类的。
	// 就不会赋值到s3的父类的成员，只赋值s3的子类成员

	// 析构函数是先调用子类的析构函数（跟栈有关系），再自动调用父类的析构函数。
	// 因为这样才能保证先析构子类，再析构父类的顺序

	//B b; // 无法调用父类的默认构造函数，直接报错

	return 0;
}